﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Printing;
using System.IO;

namespace dnnScanFreeClient
{
    public partial class dnnScanFree : Form
    {
        private System.Drawing.Bitmap m_Bitmap;

        // **** Adjustable values ****
        int intBoxSize = 30;
        int intCompletelyBlackSquare = 2280;
        int intSectionMarked = 1000;

        int intDistanceToSecondStartingPoint = 2140;

        int intBoxCodeX = 155;
        int intBoxCodeY = 62;
        int intBoxSpacing = 31;
        int intBoxExtraSpacing = 1;


        public dnnScanFree()
        {
            InitializeComponent();
        }

        #region Open Image
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();

            openFileDialog.InitialDirectory = "c:\\";
            openFileDialog.Filter = "Tif files (*.tif*)|*.tif*";
            openFileDialog.FilterIndex = 2;
            openFileDialog.RestoreDirectory = true;

            if (DialogResult.OK == openFileDialog.ShowDialog())
            {
                m_Bitmap = (Bitmap)Bitmap.FromFile(openFileDialog.FileName, false);

                // Scan must be bitonal
                if (m_Bitmap.PixelFormat != PixelFormat.Format1bppIndexed)
                {
                    MessageBox.Show("Cannot read scan. Scan must be in the Bitonal format (bit depth 1)");
                    return;
                }

                // Convert to RGB
                // Only RGB can be processed
                m_Bitmap = BitonalConverter.ConvertToRGB(m_Bitmap);

                this.AutoScroll = true;
                this.AutoScrollMinSize = new Size((int)(m_Bitmap.Width * 1), (int)(m_Bitmap.Height * 1));
                this.Invalidate();

                Point FirstStartingPoint = FindStartingPoint1(m_Bitmap, intBoxSize);
                if (FirstStartingPoint.IsEmpty)
                {
                    MessageBox.Show("Scan is unreadable. First Starting point not found");
                    return;
                }

                Point SecondStartingPoint = new Point(FirstStartingPoint.X + intDistanceToSecondStartingPoint, FirstStartingPoint.Y);
                //Debug
                DrawAColorRectangle(m_Bitmap, SecondStartingPoint, intBoxSize, intBoxSize, Color.RoyalBlue);

                // Straighten Image
                m_Bitmap = StraightenImage(m_Bitmap, FirstStartingPoint, SecondStartingPoint, intBoxSize);

                // Adjust left and right
                // Not ready yet, currently it messes up good scans
                //FirstStartingPoint = AdjustHorizontalImage(m_Bitmap, FirstStartingPoint, intBoxSize);

                // Use StartingPoint to read each block and count the pixels and determine if the block is marked or not
                string[] Answers = ReadBlocks(FirstStartingPoint, m_Bitmap);
                // Read the string of 0's and 1's and convert to decimal
                string strNumber = ConvertAnswersToBinary(Answers);
                MessageBox.Show(strNumber);
            }
        }
        #endregion

        #region AdjustHorizontalImage
        private Point AdjustHorizontalImage(Bitmap b, Point StartingPoint, int intBoxSize)
        {
            //Debug
            DrawAColorRectangle(b, StartingPoint, (intBoxSize / 2), (intBoxSize / 2), Color.DarkOrange);
            int intTmpLeftPixels = CountBlackPixelsInRectangle(b, StartingPoint, (intBoxSize / 2), (intBoxSize / 2));

            // Move starting point temporarially to the right
            // half of the width of the box
            StartingPoint.Offset((intBoxSize / 2), 0);
            //Debug
            DrawAColorRectangle(b, StartingPoint, (intBoxSize / 2), (intBoxSize / 2), Color.Purple);
            int intTmpRightPixels = CountBlackPixelsInRectangle(b, StartingPoint, (intBoxSize / 2), (intBoxSize / 2));

            if (chkDebug.Checked)
            {
                MessageBox.Show(String.Format("intTmpLeftPixels={0} | intTmpRightPixels={1} | intCompletelyBlackSquare={2}", intTmpLeftPixels.ToString(), intTmpRightPixels.ToString(), intCompletelyBlackSquare.ToString()));
            }

            // Move the StartingPoint back to it's original position
            StartingPoint.Offset(-(intBoxSize / 2), 0);

            // If both the left and right hand side of the box are more than intCompletelyBlackSquare 
            // then there is no horizontal shift
            if (intTmpLeftPixels >= intCompletelyBlackSquare & intTmpRightPixels >= intCompletelyBlackSquare)
            {
                return StartingPoint;
            }

            int intOffSet;
            // Box may be shifted to the Left
            // If Left is less than right and right is completely black
            if (intTmpLeftPixels < intTmpRightPixels | intTmpRightPixels >= intCompletelyBlackSquare)
            {
                intOffSet = Convert.ToInt16(((intCompletelyBlackSquare - intTmpLeftPixels) * .03) / 2);
                if (chkDebug.Checked)
                {
                    MessageBox.Show(String.Format("intCompletelyBlackSquare - intTmpLeftPixels = {0} | intOffSet = {1}", (intCompletelyBlackSquare - intTmpLeftPixels).ToString(), intOffSet.ToString()));
                }
                StartingPoint.Offset(intOffSet, 0);
            }

            return StartingPoint;
        }
        #endregion

        #region FindStartingPoint1
        private Point FindStartingPoint1(Bitmap b, int BoxSize)
        {
            // Find Starting Point
            Point tmpStartingPoint = new Point(0, 0);
            Point StartingPoint = LocateStartingPoint(b, tmpStartingPoint);

            //Debug
            DrawAColorRectangle(b, StartingPoint, BoxSize, BoxSize, Color.Red);

            // Count the pixels in the starting point to see if the scan will be readable
            int intTmpPixels = CountBlackPixelsInRectangle(b, StartingPoint, BoxSize, BoxSize);

            // If there are too few pixels found back up the starting point and try again
            if (intTmpPixels < intCompletelyBlackSquare)
            {
                StartingPoint.X = StartingPoint.X - 15;

                // Count the pixels again
                intTmpPixels = CountBlackPixelsInRectangle(b, StartingPoint, BoxSize, BoxSize);

                // If there are too few pixels found return a empty starting point
                if (intTmpPixels < (BoxSize * 5))
                {
                    StartingPoint = new Point();
                }
            }

            //Debug
            DrawAColorRectangle(b, StartingPoint, BoxSize, BoxSize, Color.RoyalBlue);

            return StartingPoint;
        }
        #endregion

        #region StraightenImage
        private Bitmap StraightenImage(Bitmap b, Point FirstStartingPoint, Point SecondStartingPoint, int BoxSize)
        {
            int intTmpPixels = CountBlackPixelsInRectangle(b, SecondStartingPoint, BoxSize, BoxSize);

            // Possibly rotate the image
            if (intTmpPixels < (intCompletelyBlackSquare - (BoxSize * 2)))
            {
                // The difference between a completely black square and the number of pixels found
                int intPixelsOff = intCompletelyBlackSquare - intTmpPixels;
                //ShowMessage("intPixelsOff = intCompletelyBlackSquare - intTmpPixels = " + intPixelsOff.ToString());
                // Make a reading from a higher point to determine if the scan is skewed to the left or the right
                Point tmpStartingPoint = new Point(SecondStartingPoint.X, SecondStartingPoint.Y + 10);

                //Debug
                DrawAColorRectangle(b, tmpStartingPoint, intBoxSize, intBoxSize, Color.DarkGreen);

                // Count the pixels in the new point
                int intTmpPixels2 = CountBlackPixelsInRectangle(b, tmpStartingPoint, BoxSize, BoxSize);

                // Determine if the scan is skewed to the left or the right
                float fOfsetPercentage;
                int intPixelSkew = intTmpPixels - intTmpPixels2;
                //ShowMessage("intPixelSkew = intTmpPixels - intTmpPixels2 = " + intPixelSkew.ToString());
                if (intPixelSkew > 0)
                {
                    // intPixelSkew is a positive number so the image is skewed to the left
                    fOfsetPercentage = ((float)intPixelsOff / (float)intCompletelyBlackSquare);
                }
                else
                {
                    // intPixelSkew is a negative number so the image is skewed to the right
                    fOfsetPercentage = ((float)intPixelsOff / (float)intCompletelyBlackSquare) * (float)-1;
                }

                //ShowMessage("fOfsetPercentage = " + fOfsetPercentage.ToString());
                // Rotate the scan based on the number of pixels and the direction ofthe skew
                b = RotateImage(b, fOfsetPercentage);
            }
            return b;
        }
        #endregion

        #region RotateImage
        // From http://www.codeproject.com/KB/vb/Deskew_an_Image.aspx
        private static Bitmap RotateImage(Bitmap bmp, float angle)
        {
            Graphics g;
            Bitmap tmp = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);

            tmp.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
            g = Graphics.FromImage(tmp);
            try
            {
                g.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height);
                g.RotateTransform(angle);
                g.DrawImage(bmp, 0, 0);
            }
            finally
            {
                g.Dispose();
            }
            return tmp;
        }
        #endregion

        #region ReadBlocks
        private string[] ReadBlocks(Point parmStartingPoint, Bitmap b)
        {
            string[] Answers = new string[56];
            int intPixels;
            int intAnswer = 0;
            Point tmpStartingPoint;

            //Find first block
            int intX = parmStartingPoint.X + intBoxCodeX;
            int intY = parmStartingPoint.Y + intBoxCodeY;

            int intWidth = intX;

            // There are 56 boxes. 
            // To control spacing read them in 8 groups of 7
            for (int i = 0; i <= 7; i++)
            {
                for (int ii = 0; ii <= 6; ii++)
                {
                    tmpStartingPoint = new Point(intWidth, intY);
                    DrawAColorRectangle(b, tmpStartingPoint, intBoxSize, intBoxSize, Color.RoyalBlue);
                    intPixels = CountBlackPixelsInRectangle(b, tmpStartingPoint, intBoxSize, intBoxSize);

                    // Is section marked?
                    Answers[intAnswer] = (intPixels > intSectionMarked) ? "1" : "0";
                    intAnswer++;
                    intWidth = intWidth + intBoxSpacing;
                }
                intWidth = intWidth + intBoxExtraSpacing;
            }
            return Answers;
        }
        #endregion

        #region ConvertBinaryToDecimal
        private string ConvertBinaryToDecimal(string Answer)
        {
            string strDecimalNumber = "0";
            switch (Answer)
            {
                case "0000":
                    strDecimalNumber = "0";
                    break;
                case "0001":
                    strDecimalNumber = "1";
                    break;
                case "0010":
                    strDecimalNumber = "2";
                    break;
                case "0011":
                    strDecimalNumber = "3";
                    break;
                case "0100":
                    strDecimalNumber = "4";
                    break;
                case "0101":
                    strDecimalNumber = "5";
                    break;
                case "0110":
                    strDecimalNumber = "6";
                    break;
                case "0111":
                    strDecimalNumber = "7";
                    break;
                case "1000":
                    strDecimalNumber = "8";
                    break;
                case "1001":
                    strDecimalNumber = "9";
                    break;
            }

            return strDecimalNumber;
        }
        #endregion

        #region ConvertAnswersToBinary
        private string ConvertAnswersToBinary(string[] Answers)
        {
            string strNumber = "";
            string tmpAnswer = "";
            for (int i = 0; i <= 55; i = i + 4)
            {
                tmpAnswer = Answers[i] + Answers[i + 1] + Answers[i + 2] + Answers[i + 3];
                strNumber = strNumber + ConvertBinaryToDecimal(tmpAnswer);
            }
            return strNumber;
        }
        #endregion

        #region Print
        private void printToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            PrintDocument doc = new PrintDocument();
            doc.PrintPage += this.Doc_PrintPage;

            PrintDialog dlgSettings = new PrintDialog();
            dlgSettings.Document = doc;

            if (dlgSettings.ShowDialog() == DialogResult.OK)
            {
                doc.Print();
            }
        }

        private void Doc_PrintPage(object sender, PrintPageEventArgs e)
        {
            e.Graphics.DrawImage(m_Bitmap, 0, 0);
        }
        #endregion

        #region ConvertToBinaryNumber
        private string ConvertToBinaryNumber(string strNumber)
        {
            string strBinaryString = "";
            //Read through each digit and build Binary String
            for (int i = 0; i <= 6; i++)
            {
                strBinaryString = strBinaryString + GetBinaryNumber(strNumber.Substring(i, 1));
            }

            return strBinaryString;
        }
        #endregion

        #region GetBinaryNumber
        private string GetBinaryNumber(string p)
        {
            string strBinaryNumber = "";
            switch (p)
            {
                case "0":
                    strBinaryNumber = "0000";
                    break;
                case "1":
                    strBinaryNumber = "0001";
                    break;
                case "2":
                    strBinaryNumber = "0010";
                    break;
                case "3":
                    strBinaryNumber = "0011";
                    break;
                case "4":
                    strBinaryNumber = "0100";
                    break;
                case "5":
                    strBinaryNumber = "0101";
                    break;
                case "6":
                    strBinaryNumber = "0110";
                    break;
                case "7":
                    strBinaryNumber = "0111";
                    break;
                case "8":
                    strBinaryNumber = "1000";
                    break;
                case "9":
                    strBinaryNumber = "1001";
                    break;
            }

            return strBinaryNumber;
        }
        #endregion

        #region OnPaint
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            if (m_Bitmap != null)
            {
                g.DrawImage(m_Bitmap, new Rectangle(this.AutoScrollPosition.X, this.AutoScrollPosition.Y, (int)(m_Bitmap.Width), (int)(m_Bitmap.Height)));
            }

            g.Dispose();
        }
        #endregion

        #region LocateStartingPoint
        private Point LocateStartingPoint(Bitmap b, Point tmpStartingPoint)
        {
            bool boolBreakOut = false;

            BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
            int stride = bmData.Stride;
            System.IntPtr Scan0 = bmData.Scan0;

            unsafe
            {
                byte* p = (byte*)(void*)Scan0;

                int nOffset = stride - (b.Width) * 3;
                int nWidth = (b.Width) * 3;

                for (int y = 0; y < (b.Height); ++y)
                {
                    for (int x = 0; x < nWidth; ++x)
                    {
                        if (
                            ((byte)p[0] == (byte)0)
                            & (x > 80 & y > 80)
                            & (x < b.Width + 10 & y < b.Height + 10)
                            )
                        {
                            // See if 6 pixels forward is also black
                            p = p + 6;
                            if ((byte)p[0] == (byte)0)
                            {
                                tmpStartingPoint = new Point((x / 3), y);
                                boolBreakOut = true;
                                break;
                            }
                            // It wasn't black so move back
                            p = p - 6;
                        }

                        ++p;
                    }
                    if (boolBreakOut)
                    {
                        break;
                    }
                    p += nOffset;
                }
            }

            b.UnlockBits(bmData);
            return tmpStartingPoint;
        }
        #endregion

        #region CountBlackPixelsInRectangle
        private int CountBlackPixelsInRectangle(Bitmap b, Point StartingPoint, int intXWidth, int intYHeight)
        {
            int intBlackPoints = 0;
            BitmapData bmData = b.LockBits(new Rectangle(StartingPoint.X, StartingPoint.Y, intXWidth, intYHeight), ImageLockMode.ReadOnly, b.PixelFormat);
            int stride = bmData.Stride;
            System.IntPtr Scan0 = bmData.Scan0;

            unsafe
            {
                byte* p = (byte*)(void*)Scan0;

                int nOffset = stride - (intXWidth) * 3;
                int nWidth = (intXWidth) * 3;

                for (int y = 0; y < (intYHeight); ++y)
                {
                    for (int x = 0; x < nWidth; ++x)
                    {
                        if (
                            ((byte)p[0] == (byte)0)
                            )
                        {
                            // Found a Black point
                            intBlackPoints++;
                        }
                        ++p;
                    }
                    p += nOffset;
                }
            }

            b.UnlockBits(bmData);
            return intBlackPoints;
        }
        #endregion

        //Debug

        #region ShowMessage
        private void ShowMessage(string Message)
        {
            if (!chkDebug.Checked)
            {
                return;
            }

            MessageBox.Show(Message);
        }
        #endregion

        #region DrawAColorRectangle
        private void DrawAColorRectangle(Bitmap b, Point StartingPoint, int intXWidth, int intYHeight, Color RectangleColor)
        {
            Pen p = new Pen(RectangleColor, (float)1);
            Graphics xGraph;
            xGraph = Graphics.FromImage(b);

            if (chkDebug.Checked)
            {
                xGraph.DrawRectangle(p, StartingPoint.X, StartingPoint.Y, intXWidth, intYHeight);
            }            

            xGraph.Dispose();
            p.Dispose();
            this.Invalidate();
        }
        #endregion

        #region settingsToolStripMenuItem_Click
        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AppSettings frmSettings = new AppSettings();
            frmSettings.ShowDialog();
        } 
        #endregion

        // Batch Process

        #region processToolStripMenuItem_Click
        private void processToolStripMenuItem_Click(object sender, EventArgs e)
        {
            LinqDataDataContext LinqDataDataContext = new LinqDataDataContext(Properties.Settings.Default.SqlConnectionString);
            string ImportPath = Properties.Settings.Default.ImportPath;
            string DestinationPath = Properties.Settings.Default.DestinationPath;
            string tempDocumentID = "";
            int DocumentID, DocumentCheckNumber;

            bool tmpBoolDebug = chkDebug.Checked;
            chkDebug.Checked = false;
            DirectoryInfo di = new DirectoryInfo(ImportPath);
            FileInfo[] Files = di.GetFiles("*.tif*");

            List<String> ColFiles = new List<String>();
            foreach (FileInfo File in Files)
            {
                ColFiles.Add(File.Name);
            }

            foreach (String strFile in ColFiles)
            {
                // Process the file
                m_Bitmap = (Bitmap)Bitmap.FromFile(ImportPath + "\\" + strFile, false);
                tempDocumentID = ProcessScan(m_Bitmap);

                if (tempDocumentID.Length > 14)
                {
                    break;
                }

                DocumentID = Convert.ToInt32(tempDocumentID.Substring(0, 10));
                DocumentCheckNumber = Convert.ToInt32(tempDocumentID.Substring(10, 4));

                var results = (from ScanFreeDocuments in LinqDataDataContext.dnnScanFreeDocuments
                               where ScanFreeDocuments.DocumentID == DocumentID &
                               ScanFreeDocuments.DocumentCheckNumber == DocumentCheckNumber
                               select ScanFreeDocuments).FirstOrDefault();

                // If document not found set DocumentID to 0
                if (results == null)
                {
                    DocumentID = 0;
                }

                dnnScanFreeFile dnnScanFreeFile = new dnnScanFreeFile();
                dnnScanFreeFile.DocumentID = DocumentID;
                dnnScanFreeFile.FileName = strFile;
                LinqDataDataContext.dnnScanFreeFiles.InsertOnSubmit(dnnScanFreeFile);
                LinqDataDataContext.SubmitChanges();

                m_Bitmap.Dispose();
                m_Bitmap = null;

                // move file
                File.Move(ImportPath + "\\" + strFile, DestinationPath + "\\" + strFile);
            }

            chkDebug.Checked = tmpBoolDebug;
            MessageBox.Show("Process Completed");
        } 
        #endregion

        #region ProcessScan
        private string ProcessScan(Bitmap m_Bitmap)
        {
            // Scan must be bitonal
            if (m_Bitmap.PixelFormat != PixelFormat.Format1bppIndexed)
            {
                return "Cannot read scan. Scan must be in the Bitonal format (bit depth 1)";
            }

            // Convert to RGB
            // Only RGB can be processed
            m_Bitmap = BitonalConverter.ConvertToRGB(m_Bitmap);

            Point FirstStartingPoint = FindStartingPoint1(m_Bitmap, intBoxSize);
            if (FirstStartingPoint.IsEmpty)
            {
                return "Scan is unreadable. First Starting point not found";
            }

            Point SecondStartingPoint = new Point(FirstStartingPoint.X + intDistanceToSecondStartingPoint, FirstStartingPoint.Y);

            // Straighten Image
            m_Bitmap = StraightenImage(m_Bitmap, FirstStartingPoint, SecondStartingPoint, intBoxSize);

            // Use StartingPoint to read each block and count the pixels and determine if the block is marked or not
            string[] Answers = ReadBlocks(FirstStartingPoint, m_Bitmap);
            // Read the string of 0's and 1's and convert to decimal
            string strNumber = ConvertAnswersToBinary(Answers);
            return strNumber;
        } 
        #endregion
    }
}
